package ar.uba.fi.tonyvaliente.tree;

import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;

/**
 * Clase principal del árbol, no se especifica si es en memoria o en disco.
 * Se parametrizan las clases de la clave y el valor
 * @author santiago
 *
 * @param <K> tipo de la clave
 * @param <V> tipo del valor
 */
public abstract class BPlusTree <K extends Comparable<K>, V> {
	
	private static Logger log = Logger.getLogger(BPlusTree.class.getName());
	
	/**
	 * máxima cantidad de elementos por nodo. 
	 */
	private int elementosPorNodo;

	private Set<Reference<K, V>> dirtyNodes = new HashSet<Reference<K,V>>();
	
	/**
	 * Referencia al nodo raiz.
	 */
	private Reference<K, V> root;
	
	private Reference<K, V> first;
	
	private BPlusTreeNode<K, V> rootNode;
	
	
	/**
	 * Crea una referencia a un nodo de acuerdo al tipo de árbol
	 * que se está manejando.
	 * 
	 * @return
	 */
	protected abstract Reference<K, V> createReference();
	
	
	/**
	 * Añade el par clave valor al árbol.
	 * @param key
	 * @param value
	 */
	public void add(K key, V value) throws BPlusTreeException {
		
		if(this.getRootNode() == null){
			BPlusTreeNode<K, V> rootN = this.getRoot().getNode();
			rootN.setTree(this);
			this.rootNode = rootN;
			
		}
		
		BPlusTreeNode<K, V>.AddResult<K, V> result = this.rootNode.add(key, value);
		
		if(result != null){
			Reference<K, V> newRoot = this.createReference();
			InnerBPlusTreeNode<K, V> newRootNode = new InnerBPlusTreeNode<K, V>(null, this, this.getElementosPorNodo());
			
			newRootNode.cantidadElementos = 1;
			newRootNode.getKeys().add((K)result.getKey());
			newRootNode.getReferences().add(0, result.getLeftReference());
			newRootNode.getReferences().add(1, result.getRightReference());
			
			newRoot.setNode(newRootNode);
			this.setRoot(newRoot);
			rootNode = newRootNode;
			dirtyNodes.add(root);
		}
		
		for (Reference<K, V> reference : dirtyNodes) {
			reference.updateNode();
		}
		dirtyNodes.removeAll(dirtyNodes);

//		log.finest("root: " + this.getRoot());

	}
	
	
	/**
	 * Dada una clave obtiene su valor.
	 * 
	 * @param key clave a buscar
	 * @return valor asociado a la clave.
	 */
	public V get(K key) throws BPlusTreeException, NoSuchElementException{
		return this.getRoot().getNode().get(key);
	}


	public int getElementosPorNodo() {
		return elementosPorNodo;
	}


	public void setElementosPorNodo(int elementosPorNodo) {
		this.elementosPorNodo = elementosPorNodo;
	}


	protected Set<Reference<K, V>> getDirtyNodes(){
		return this.dirtyNodes;
	}
	
	protected Reference<K, V> getRoot() throws BPlusTreeException{
		return this.root;
	}


	protected void setRoot(Reference<K, V> root) throws BPlusTreeException{
		this.root = root;
	}


	protected BPlusTreeNode<K, V> getRootNode() {
		if(rootNode != null)
			rootNode.tree = this;
		return rootNode;
	}


	protected void setRootNode(BPlusTreeNode<K, V> rootNode) {
		this.rootNode = rootNode;
		if(this.rootNode != null)
			this.rootNode.tree = this;
	}


	/**
	 * cerramos los recursos utilizados.
	 * @throws BPlusTreeException 
	 */
	public abstract void close() throws BPlusTreeException;


	public Reference<K, V> getFirst() throws BPlusTreeException {
		return first;
	}


	protected void setFirst(Reference<K, V> first) throws BPlusTreeException {
		this.first = first;
	}

	public abstract Iterator<K> iterator();
	
	protected class BPlusTreeIterator implements Iterator<K>{

		@Override
		public boolean hasNext() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public K next() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException("No está permitido eliminar claves en esta implementación de árbol");
			
		}
		
	}

}
